#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <math.h>
#include <limits.h>
#include "cpu.h"
#include "mem.h"
#include "rdtsc.h"

//#define DEBUG
//#define CREATE_FILE
//#define READ
#define MMAP
//#define WALK
#define COPY
#define MEGABYTE (1024*1024)
#define REPEAT 25
#define FILE_SIZE 0
#define FILE_NUM 25
#define FILE_NAME "./testfiles/0MB/foo"
#define FILE_EXT ".txt"

char* itoa(int val)
{
	if(val == 0)
	{
		return "0";
	}

	int digit = log10(val) + 1;

	#ifdef DEBUG
	printf("digit number = %d\n", digit);
	#endif

	char* integer = (char*) malloc(sizeof(char)*(digit+1)); 
	int k = val;
	int j = pow(10, digit - 1);
	int i = 0;
	while(i < digit)
	{
		*(integer+i) = '0' + k/j;
		
		/*
		#ifdef DEBUG
		printf("i = %d\n", i);
		printf("j = %d\n", j);
		printf("k = %d\n", k);
		printf("k/j = %d\n", k/j);
		printf("digit = %c\n", *(integer+i));
		#endif
		*/

		k %= j;
		j /= 10;
		i++;
	}
	*(integer+i) = '\0';

	#ifdef DEBUG
	printf("integer string = %s\n", integer);
	#endif

	return integer;
}

int write_file(unsigned long long size, int index)
{
	unsigned long long fsize = size;
	char* findex = itoa(index);

	#ifdef DEBUG
	printf("file size = %llu\n", fsize);
	printf("file index = %s\n", findex);
	#endif

	char* fname = (char*) malloc(sizeof(char)*128);
	*fname = '\0';
	fname = strcat(fname, FILE_NAME);
	fname = strcat(fname, findex);
	fname = strcat(fname, FILE_EXT);

	#ifdef DEBUG
	printf("file name = %s\n", fname);
	#endif

	unsigned char* buffer = (char*) malloc(sizeof(unsigned char)*fsize);
	if(buffer == NULL)
	{
		fprintf(stderr, "malloc failure\n");
		return -1;
	}
	memset(buffer, '0', sizeof(unsigned char)*fsize);

	int fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
	if(fd == -1)
	{
		fprintf(stderr, "file open failure: %s\n", fname);
		return -1;
	}
	//	int cnt = write(fd, buffer, fsize);
	int cnt = write(fd, buffer, fsize);
	if(cnt == -1)
	{
		fprintf(stderr, "write to file failure: %s\n", fname);
		close(fd);
		free(buffer);
		return -1;
	}
	close(fd);
	free(buffer);
	free(fname);

	return 1;
}

int read_file(unsigned long long size, int index)
{
	//	timing mechanism
	unsigned long long cycle_start = 0;
	unsigned long long cycle_end = 0;
	
	//	generate file names	
	unsigned long long fsize = size;
	char* findex = itoa(index);

	#ifdef DEBUG
	printf("file size = %llu\n", fsize);
	printf("file index = %s\n", findex);
	#endif

	char* fname = (char*) malloc(sizeof(char)*128);
	*fname = '\0';
	fname = strcat(fname, FILE_NAME);
	fname = strcat(fname, findex);
	fname = strcat(fname, FILE_EXT);

	#ifdef DEBUG
	printf("file name = %s\n", fname);
	#endif

	//	buffer to hold file content
	unsigned char* buffer = (char*) malloc(sizeof(unsigned char)*fsize);
	if(buffer == NULL)
	{
		fprintf(stderr, "malloc failure\n");
		return -1;
	}

	//	open file
	int fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
	if(fd == -1)
	{
		fprintf(stderr, "file open failure: %s\n", fname);
		return -1;
	}

	//	read the whole file into buffer and use rdtsc() to time
	cycle_start = rdtsc();
	int cnt = write(fd, buffer, fsize);
	cycle_end = rdtsc();
	
	#ifdef DEBUG
	printf("start cycle = %llu\n", cycle_start);
	printf("end cycle = %llu\n", cycle_end);
	#endif
	free(buffer);
	free(fname);

	// output the result only if the read succeed
	if(cnt == -1)
	{
		fprintf(stderr, "write to file failure: %s\n", fname);
		return -1;
	}
	printf("%lf\n", (double)(cycle_end - cycle_start)*1000 / (double) CPU_FQ);

	return 1;

}

int mmap_file(unsigned long long size, int index)
{
	//	timing mechanism
	unsigned long long cycle_start = 0;
	unsigned long long cycle_end = 0;
	
	//	generate file names	
	unsigned long long fsize = size;
	char* findex = itoa(index);

	#ifdef DEBUG
	printf("file size = %llu\n", fsize);
	printf("file index = %s\n", findex);
	#endif

	char* fname = (char*) malloc(sizeof(char)*128);
	*fname = '\0';
	fname = strcat(fname, FILE_NAME);
	fname = strcat(fname, findex);
	fname = strcat(fname, FILE_EXT);

	#ifdef DEBUG
	printf("file name = %s\n", fname);
	#endif

	//	buffer to hold file content
	
	unsigned char* buffer = (char*) malloc(sizeof(unsigned char)*fsize);
	if(buffer == NULL)
	{
		fprintf(stderr, "malloc failure\n");
		return -1;
	}
	
	unsigned char* buf_map = (char*) malloc(sizeof(unsigned char)*fsize);
	if(buf_map == NULL)
	{
		fprintf(stderr, "malloc failure\n");
		return -1;
	}
	

	//	open file
	int fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
	if(fd == -1)
	{
		fprintf(stderr, "file open failure: %s\n", fname);
		return -1;
	}

	//	map file to buf_map and then copy to buffer, use rdtsc() to time
	unsigned char tmp;
	cycle_start = rdtsc();
	unsigned char* fptr = mmap(buf_map, fsize, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
	
	//	memory copy
	#ifdef COPY	
	memcpy(buffer, buf_map, fsize);
	#endif
	
	//	memory walk
	#ifdef WALK
	for(int i = 0; i < fsize; i++)
		tmp = *(fptr + i);
	#endif

	cycle_end = rdtsc();

	#ifdef DEBUG
	printf("start cycle = %llu\n", cycle_start);
	printf("end cycle = %llu\n", cycle_end);
	#endif

	free(buffer);
	free(buf_map);
	free(fname);

	printf("%lf\n", (double)(cycle_end - cycle_start)*1000 / (double) CPU_FQ);
	return 1;

}


int main(int argc, char* argv[])
{
	if(argc != 1)
	{
		perror("invalid arguments");
		exit(-1);
	}

	// create test files
	#ifdef CREATE_FILE
	unsigned long long fsize = FILE_SIZE  * MEGABYTE;
	for(int findex = 0; findex < FILE_NUM; findex++)
	{
		write_file(fsize, findex);
	}
	#endif

	//	measure the time to read from a large file using read()
	#ifdef READ
	unsigned long long fsize = FILE_SIZE * MEGABYTE;
	// read from disk
	printf("read from disk:\n");
	for(int findex = 0; findex < FILE_NUM; findex ++)
	{
		read_file(fsize, findex);
	}
	// read from cache
	printf("read from cache:\n");
	for(int cnt = 0; cnt < REPEAT; cnt ++)
	{
		read_file(fsize, FILE_NUM-1);
	}
	#endif

	
	//	measure the time to read from a large file using mmap()
	#ifdef MMAP
	unsigned long long fsize = FILE_SIZE * MEGABYTE;
	//	mmap from disk
	printf("mmap from disk:\n");
	for(int findex = 0; findex < FILE_NUM; findex ++)
	{
		mmap_file(fsize, findex);
	}
	//	mmap from cache
	printf("mmap from cache:\n");
	for(int cnt = 0; cnt < REPEAT; cnt ++)
	{
		mmap_file(fsize, FILE_NUM-1);
	}
	#endif

	
	return 1;
}
